(function (window, voice) {
  'use strict'
  if (typeof define === 'function' && define.amd) {
    define(voice)
  } else if (typeof exports === 'object') {
    module.exports = voice()
  } else {
    window.Voice = voice()
  }
}(typeof window !== 'undefined' ? window : this, () => {
  'use strict'
  return class IatRecorder {
    constructor (opts = {}) {
      // 服务接口认证信息(语音听写（流式版）WebAPI)
      this.appId = opts.appId || ''
      this.apiKey = opts.apiKey || ''
      this.apiSecret = opts.apiSecret || ''
      this.workerJs = opts.workerJs || ''
      this.patchId = opts.patchId || '' // 模型id
      // 识别监听方法
      this.onTextChange = opts.onTextChange || Function()
      this.onWillStatusChange = opts.onWillStatusChange || Function()
      // 方言/语种
      this.status = 'null'
      this.language = opts.language || 'zh_cn'
      this.accent = opts.accent || 'mandarin'
      // 流媒体
      this.streamRef = []
      // 记录音频数据
      this.audioData = []
      // 记录听写结果
      this.resultText = ''
      // wpgs下的听写结果需要中间状态辅助记录
      this.resultTextTemp = ''
      // 音频数据多线程
      this.init(this.workerJs)
    }
    // WebSocket请求地址鉴权
    getWebSocketUrl () {
      const self = this
      return new Promise((resolve, reject) => {
        // 请求地址根据语种不同变化
        try {
          const CryptoJS = require('crypto-js')
          // ws-api-hu.xfyun.cn/v2/iat
          let url = 'wss://iat-api.xfyun.cn/v2/iat',
            host = 'iat-api.xfyun.cn',
            date = new Date().toGMTString(),
            algorithm = 'hmac-sha256',
            headers = 'host date request-line',
            signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`,
            signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret),
            signature = CryptoJS.enc.Base64.stringify(signatureSha),
            authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`,
            authorization = btoa(authorizationOrigin)
          if(self.patchId){
            url = 'wss://ws-api-hu.xfyun.cn/v2/iat'
          }
          const patchIdInfo = !self.patchId?'':`&domain=iat_patch&patch_id=${self.patchId}`
          url = `${url}?authorization=${authorization}&date=${date}&host=${host}${patchIdInfo}`
          resolve(url)
        } catch (error) {
          let url = 'wss://iat-api.xfyun.cn/v2/iat',
            host = 'iat-api.xfyun.cn',
            date = new Date().toGMTString(),
            algorithm = 'hmac-sha256',
            headers = 'host date request-line',
            signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`,
            signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret),
            signature = CryptoJS.enc.Base64.stringify(signatureSha),
            authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`,
            authorization = btoa(authorizationOrigin)
          if(self.patchId){
            url = 'wss://ws-api-hu.xfyun.cn/v2/iat'
            // host = 'ws-api-hu.xfyun.cn'
          }
          const patchIdInfo = !self.patchId?'':`&domain=iat_patch&patch_id=${self.patchId}`
          url=`${url}?authorization=${authorization}&date=${date}&host=${host}${patchIdInfo}`
          resolve(url)
        }
      })
    }
    // 操作初始化
    init () {
      const self = this
      try {
        if (!self.appId || !self.apiKey || !self.apiSecret || !self.workerJs) {
          alert('请正确配置【迅飞语音听写（流式版）WebAPI】服务接口认证信息！')
        } else {
          self.webWorker = new Worker(this.workerJs)
          self.webWorker.onmessage = function (event) {
            self.audioData.push(...event.data)
          }
        }
      } catch (error) {
        alert('对不起：请在服务器环境下运行！')
        console.error('请在服务器如：WAMP、XAMPP、Phpstudy、http-server、WebServer等环境中运行！', error)
      }
      console.log('%c ❤️使用说明：http://www.muguilin.com/blog/info/609bafc50d572b3fd79b058f', 'font-size:32px; color:blue; font-weight: bold;')
    }
    // 修改录音听写状态
    setStatus (status) {
      this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status)
      this.status = status
    }
    // 设置识别结果内容
    setResultText ({ resultText, resultTextTemp } = {}) {
      this.onTextChange && this.onTextChange(resultTextTemp || resultText || '')
      resultText !== undefined && (this.resultText = resultText)
      resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp)
    }
    // 修改听写参数
    setParams ({ language, accent } = {}) {
      language && (this.language = language)
      accent && (this.accent = accent)
    }
    // 对处理后的音频数据进行base64编码，
    toBase64 (buffer) {
      let binary = ''
      let bytes = new Uint8Array(buffer)
      for (let i = 0; i < bytes.byteLength; i++) {
        binary += String.fromCharCode(bytes[i])
      }
      return window.btoa(binary)
    }
    // 连接WebSocket
    connectWebSocket () {
      return this.getWebSocketUrl().then(url => {
        console.log('WebSocket',url)
        let iatWS
        if ('WebSocket' in window) {
          iatWS = new WebSocket(url)
        } else if ('MozWebSocket' in window) {
          iatWS = new MozWebSocket(url)
        } else {
          alert('浏览器不支持WebSocket!')
          return false
        }
        this.webSocket = iatWS
        this.setStatus('init')
        iatWS.onopen = e => {
          this.setStatus('ing')
          // 重新开始录音
          setTimeout(() => {
            this.webSocketSend()
          }, 500)
        }
        iatWS.onmessage = e => {
          this.webSocketRes(e.data)
        }
        iatWS.onerror = e => {
          this.recorderStop(e)
        }
        iatWS.onclose = e => {
          this.recorderStop(e)
        }
      })
    }
    // 初始化浏览器录音
    recorderInit () {
      // 创建音频环境
      try {
        this.audioContext = this.audioContext ? this.audioContext : new (window.AudioContext || window.webkitAudioContext)()
        this.audioContext.resume()
        if (!this.audioContext) {
          alert('浏览器不支持webAudioApi相关接口')
          return false
        }
      } catch (e) {
        if (!this.audioContext) {
          alert('浏览器不支持webAudioApi相关接口')
          return false
        }
      }
      // 获取浏览器录音权限成功时回调
      let getMediaSuccess = _ => {
        // 创建一个用于通过JavaScript直接处理音频
        this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1)
        this.scriptProcessor.onaudioprocess = e => {
          if (this.status === 'ing') {
            // 多线程音频数据处理
            try {
              this.webWorker.postMessage(e.inputBuffer.getChannelData(0))
            } catch (error) { }
          }
        }
        // 创建一个新的MediaStreamAudioSourceNode 对象，使来自MediaStream的音频可以被播放和操作
        this.mediaSource = this.audioContext.createMediaStreamSource(this.streamRef)
        this.mediaSource.connect(this.scriptProcessor)
        this.scriptProcessor.connect(this.audioContext.destination)
        this.connectWebSocket()
      }
      // 获取浏览器录音权限失败时回调
      let getMediaFail = e => {
        alert('对不起：录音权限获取失败!')
        this.audioContext && this.audioContext.close()
        this.audioContext = undefined
        // 关闭websocket
        if (this.webSocket && this.webSocket.readyState === 1) {
          this.webSocket.close()
        }
      }
      navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
      // 获取浏览器录音权限
      if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia({
          audio: true
        }).then(stream => {
          this.streamRef = stream
          getMediaSuccess()
        }).catch(e => {
          getMediaFail(e)
        })
      } else if (navigator.getUserMedia) {
        navigator.getUserMedia({
          audio: true
        }, stream => {
          this.streamRef = stream
          getMediaSuccess()
        }, function (e) {
          getMediaFail(e)
        })
      } else {
        if (navigator.userAgent.toLowerCase().match(/chrome/) && location.origin.indexOf('https://') < 0) {
          console.error('获取浏览器录音功能，因安全性问题，需要在localhost 或 127.0.0.1 或 https 下才能获取权限！')
        } else {
          alert('对不起：未识别到录音设备!')
        }
        this.audioContext && this.audioContext.close()
        return false
      }
    }
    // 向webSocket发送数据(音频二进制数据经过Base64处理)
    webSocketSend () {
      if (this.webSocket.readyState !== 1) return false
      // 音频数据
      const audioData = this.audioData.splice(0, 1280)
      const params = {
        common: {
          app_id: this.appId,
        },
        business: {
          language: this.language, //小语种可在控制台--语音听写（流式）--方言/语种处添加试用
          domain: this.patchId?'iat_patch':'iat',
          accent: this.accent, //中文方言可在控制台--语音听写（流式）--方言/语种处添加试用
          vad_eos: 5000,
          dwa: 'wpgs', //为使该功能生效，需到控制台开通动态修正功能（该功能免费）
          patch_id:this.patchId
        },
        data: {
          status: 0,
          format: 'audio/L16;rate=16000',
          encoding: 'raw',
          audio: this.toBase64(audioData)
        }
      }
      // 发送数据
      this.webSocket.send(JSON.stringify(params))
      this.handlerInterval = setInterval(() => {
        // websocket未连接
        if (this.webSocket.readyState !== 1) {
          this.audioData = []
          clearInterval(this.handlerInterval)
          return false
        }
        if (this.audioData.length === 0) {
          if (this.status === 'end') {
            this.webSocket.send(
              JSON.stringify({
                data: {
                  status: 2,
                  format: 'audio/L16;rate=16000',
                  encoding: 'raw',
                  audio: ''
                }
              })
            )
            this.audioData = []
            clearInterval(this.handlerInterval)
          }
          return false
        }
        // 中间帧
        this.webSocket.send(
          JSON.stringify({
            data: {
              status: 1,
              format: 'audio/L16;rate=16000',
              encoding: 'raw',
              audio: this.toBase64(this.audioData.splice(0, 1280))
            }
          })
        )
      }, 40)
    }
    // 识别结束 webSocket返回数据
    webSocketRes (resultData) {
      let jsonData = JSON.parse(resultData)
      if (jsonData.data && jsonData.data.result) {
        let data = jsonData.data.result
        let str = ''
        let ws = data.ws
        for (let i = 0; i < ws.length; i++) {
          str = str + ws[i].cw[0].w
        }
        // 开启wpgs会有此字段(前提：在控制台开通动态修正功能)
        // 取值为 "apd"时表示该片结果是追加到前面的最终结果；取值为"rpl" 时表示替换前面的部分结果，替换范围为rg字段
        if (data.pgs) {
          if (data.pgs === 'apd') {
            // 将resultTextTemp同步给resultText
            this.setResultText({
              resultText: this.resultTextTemp
            })
          }
          // 将结果存储在resultTextTemp中
          this.setResultText({
            resultTextTemp: this.resultText + str
          })
        } else {
          this.setResultText({
            resultText: this.resultText + str
          })
        }
      }
      if (jsonData.code === 0 && jsonData.data.status === 2) {
        this.webSocket.close()
      }
      if (jsonData.code !== 0) {
        this.webSocket.close()
      }
    }
    // 启动录音
    recorderStart () {
      if (!this.audioContext) {
        this.recorderInit()
      } else {
        this.audioContext.resume()
        this.connectWebSocket()
      }
    }
    // 停止录音
    recorderStop () {
      if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen))) {
        // safari下suspend后再次resume录音内容将是空白，设置safari下不做suspend
        this.audioContext && this.audioContext.suspend()
      }
      this.setStatus('end')
      try {
        // this.streamRef.getTracks().map(track => track.stop()) || his.streamRef.getAudioTracks()[0].stop();
      } catch (error) {
        console.error('暂停失败!')
      }
    }
    // 开始
    start () {
      this.recorderStart()
      this.setResultText({ resultText: '', resultTextTemp: '' })
    }
    // 停止
    stop () {
      this.recorderStop()
    }
  }
}))
